#!/bin/bash

usage="\
Usage:
        oc-ops io latency -d device [-s size] [-l logdir] [-a average] [-m max] [-p period] [-Q threshold]  [-D threshold] [-r] [-k] [-h]
        COMMAND-LINE Options:
	-d	指定监控的设备，如：sda ；多个设备之间用逗号相连，如：sda,sdb
        -s	指定日志存储空间（针对-d参数中的每个设备）的大小，超过大小后，日志将自动回滚 （超过阈值的日志仍会保留）, 默认1048576KB
	-l,	指定日志存储目录，需按绝对路径传递存储目录；默认是 /data/oc-ops/io/latency
		为便于校验目录合法性、避免进入其他目录创建和删除文件，-l指定的路径中必须包含"io/latency"字符串
	-a	io average latency的阈值，支持浮点表示，单位为秒，默认0.2s
	-m	io max latency的阈值，支持浮点表示，单位为秒，默认5s
	-p	每个监控周期的时间，单位为秒，默认60s （备注：一个监控周期结束，会自动开始下一个周期的监控）
	-Q	IO Q2C（表示io Queued To Complete）耗时的阈值，支持浮点表示，单位为秒，默认与 -m 的数值相同
	-D	IO D2C（表示io Dispatched/Issued To Complete）耗时的阈值 ，支持浮点表示，单位为秒，默认与 -m 的数值相同
	-r	保留中间过程的日志，使用此参数可便于调试或基于保留的日志更深入的分析IO时延问题
		一但有 oc-ops io latency 命令没有带-r参数，则此次采样过程种的所有blktrace数据不会保留
	-k	kill 所有oc-ops io latency监控io的进程，结束监控
		使用-k时，如想保留blktrace数据进一步分析，也需加上-r参数; 否则将清空历次采用的所有的blktrace数据
	-h	显示oc-ops io latency的用法

使用注意项：
	-l 指定的日志存储目录，尽量别在-d指定的监控的设备上，因为日志存储会产生io、加大被监控设备的io压力；
	-l 指定的日志存储目录，尽量别指定内存文件系统（如tmpfs）所在的目录，因为在内存文件系统上存储日志会消耗内存，对业务产生干扰；
	-d 可支持监控多个device，如果监控过多设备（特别是快速设备，如nvme），会对cpu占用率产生干扰。
"

if (( $# < 1 )); then
	echo "$usage"
	exit 1
fi

device_array=()
export perdevlog_store_size=1048576
export logdir="/data/oc-ops/io/latency"
export average_latency_threshold=0.2
export max_latency_threshold=5
export period_time=60
export Q2C_threshold=0
export D2C_threshold=0
export remain_data=0
export find_exceed_io=0

export curr_dir=$(pwd)
export work_dir=$(readlink /proc/$$/fd/255); work_dir=$(dirname $work_dir); cd "$work_dir"
export run_log="$logdir/run_log"

source lib.sh
set_cgroup_rights

kill_sample=0

get_device_name()
{
	device_array="$@"
}

check_logdir()
{
	echo "$logdir" | grep ^[/] > /dev/null ; local ret_val=$?
	if (( $ret_val != 0 )); then echo "Must using absolute path!";  return 1 ; fi

	echo "$logdir" | grep "io/latency" > /dev/null ; ret_val=$?
	if (( $ret_val != 0 )); then echo "Path must including \"io/latency\" substring!"; return 1 ; fi

	echo "$logdir" | grep -E '[ |        ]' > /dev/null ; ret_val=$?
	if (( $ret_val == 0 )); then echo "Path must not including space and tab char!"; return 1 ; fi

	return 0
}

safe_rm()
{
	echo "$PWD/" | grep "$logdir" > /dev/null ; local ret_val=$?
	if (( $ret_val != 0 )); then echo "Forbid rm outside the $logdir dir!";  return 1 ; fi

	echo "$@" | grep "/" > /dev/null ; ret_val=$?
	if (( $ret_val == 0 )); then echo "Forbid having \"/\" in args!";  return 1 ; fi

	rm $@ 2>/dev/null
}
export -f safe_rm

while getopts 'd:s:l:a:m:p:Q:D:rkh' OPT; do
        case $OPT in
                d) 	param_tmp="$OPTARG"	
			get_device_name $( echo $param_tmp | sed 's/,/ /g' )
                        ;;
		s)	perdevlog_store_size="$OPTARG"
			;;
		l)	logdir="$OPTARG"
			check_logdir
			if (( $? != 0)); then
				echo "The dir \"$logdir\" is illegal!"
				exit 1
			fi
			;;
		a)	average_latency_threshold="$OPTARG"
			;;
		m)	max_latency_threshold="$OPTARG"
			;;
		p)	period_time="$OPTARG"
			;;
		Q)	Q2C_threshold="$OPTARG"
			find_exceed_io=1
			;;
		D)	D2C_threshold="$OPTARG"
			find_exceed_io=1
			;;
		r)	remain_data=1
			;;
		k)	kill_sample=1
			;;
                h)	echo "$usage"
                        exit $?
			;;
                ?)	echo "Invalid option: $OPT" >&2
                        echo "$usage"
                        exit 1
			;;
        esac
done

mkdir -p "$logdir"
cd "$logdir" && if [ -e kill.sh ]; then ./kill.sh -l "$logdir" -r $remain_data ; fi
if (( kill_sample == 1 )); then
	exit 0
fi

## del it in the later
cp -a "$work_dir"/*.sh "$logdir"

div=2
half=`expr $perdevlog_store_size / $div`

echo "Will display IO sampling result every $period_time, please wait..."
for devname in ${device_array[@]}
do
	if [ -e /dev/$devname ]; then
		## run at here.
		./run.sh $devname $half &
	else
		echo "Error param! Device name is $devname, but there isn't /dev/$devname !"
		echo "All params: $@"
		exit 1
	fi
done
